package com.ian.util;

import com.ian.enumeration.MediaFormat;
import com.ian.m3u8.exception.M3u8Exception;
import com.ian.m3u8.listener.DownloadListener;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.security.Security;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @desc m3u8工具类
 * 
 * @author Witt
 * @version 1.0.0
 * @date 2022/11/10 23:25
 */
@Slf4j
public class M3u8Utils {

    /**
     * 解决java不支持AES/CBC/PKCS7Padding模式解密
     */
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private String downloadUrl;

    //优化内存占用
    private static final BlockingQueue<byte[]> BLOCKING_QUEUE = new LinkedBlockingQueue<>();

    //线程数
    private Integer threadCount = 1;

    //重试次数
    private Integer retryCount = 50;

    //链接连接超时时间（单位：毫秒）
    private long timeoutMillisecond = 1000L;

    //合并后的文件存储目录
    private String dir;

    //合并后的视频文件名称
    private String fileName;

    //已完成ts片段个数
    private Integer finishedCount = 0;

    //解密算法名称
    private String method;

    //密钥
    private String key = "";

    //密钥字节
    private byte[] keyBytes = new byte[16];

    //key是否为字节
    private boolean isByte = false;

    //IV
    private String iv = "";

    //所有ts片段下载链接
    private Set<String> tsSet = new LinkedHashSet<>();

    //解密后的片段
    private Set<File> finishedFiles = new ConcurrentSkipListSet<>(Comparator.comparingInt(o -> Integer.parseInt(o.getName().replace(".xyz", ""))));

    //已经下载的文件大小
    private BigDecimal downloadBytes = new BigDecimal(0);

    //监听间隔
    private volatile long interval = 0L;

    //自定义请求头
    private Map<String, Object> requestHeaderMap = new HashMap<>();
    ;

    //监听事件
    private Set<DownloadListener> listenerSet = new HashSet<>(5);

    //代理设置
    private Proxy proxy;

    /**
     * 检查字段
     */
    private void checkField() {
        if ("m3u8".compareTo(MediaFormat.getMediaFormat(downloadUrl)) != 0) {
            throw new M3u8Exception(downloadUrl + "不是一个完整m3u8链接！");
        }
        if (threadCount <= 0) {
            throw new M3u8Exception("同时下载线程数只能大于0！");
        }
        if (retryCount < 0) {
            throw new M3u8Exception("重试次数不能小于0！");
        }
        if (timeoutMillisecond < 0) {
            throw new M3u8Exception("超时时间不能小于0！");
        }
        if (StringUtils.isBlank(dir)) {
            throw new M3u8Exception("视频存储目录不能为空！");
        }
        if (StringUtils.isBlank(fileName)) {
            throw new M3u8Exception("视频名称不能为空！");
        }
        finishedCount = 0;
        method = "";
        key = "";
        isByte = false;
        iv = "";
        tsSet.clear();
        finishedFiles.clear();
        downloadBytes = new BigDecimal(0);
    }

    /**
     * 获取所有的ts片段下载链接
     *
     * @return 链接是否被加密，null为非加密
     */
    private String getTsUrl() {
        StringBuilder content = getUrlContent(downloadUrl, false);
        //判断是否是m3u8链接
        if (!content.toString().contains("#EXTM3U")) {
            throw new M3u8Exception(downloadUrl + "不是m3u8链接！");
        }
        String[] split = content.toString().split("\\n");
        String keyUrl = "";
        boolean isKey = false;
        for (String s : split) {
            //如果含有此字段，则说明只有一层m3u8链接
            if (s.contains("#EXT-X-KEY") || s.contains("#EXTINF")) {
                isKey = true;
                keyUrl = downloadUrl;
                break;
            }
            //如果含有此字段，则说明ts片段链接需要从第二个m3u8链接获取
            if (s.contains(".m3u8")) {
                if (StringUtils.isUrl(s)) {
                    return s;
                }
                String relativeUrl = downloadUrl.substring(0, downloadUrl.lastIndexOf("/") + 1);
                if (s.startsWith("/")) {
                    s = s.replaceFirst("/", "");
                }
                keyUrl = mergeUrl(relativeUrl, s);
                break;
            }
        }
        if (StringUtils.isBlank(keyUrl)) {
            throw new M3u8Exception("未发现key链接！");
        }
        //获取密钥
        String key1 = isKey ? getKey(keyUrl, content) : getKey(keyUrl, null);
        if (StringUtils.isNotEmpty(key1)) {
            key = key1;
        } else {
            key = null;
        }
        return key;
    }

    /**
     * 模拟http请求获取内容
     *
     * @param urls  http链接
     * @param isKey 这个url链接是否用于获取key
     * @return 内容
     */
    private StringBuilder getUrlContent(String urls, boolean isKey) {
        int count = 1;
        HttpURLConnection httpURLConnection = null;
        StringBuilder content = new StringBuilder();
        while (count <= retryCount) {
            try {
                URL url = new URL(urls);
                if (proxy ==null) {
                    httpURLConnection = (HttpURLConnection) url.openConnection();
                }else {
                    httpURLConnection = (HttpURLConnection) url.openConnection(proxy);
                }
                httpURLConnection.setConnectTimeout((int) timeoutMillisecond);
                httpURLConnection.setReadTimeout((int) timeoutMillisecond);
                httpURLConnection.setUseCaches(false);
                httpURLConnection.setDoInput(true);
                for (Map.Entry<String, Object> entry : requestHeaderMap.entrySet()) {
                    httpURLConnection.addRequestProperty(entry.getKey(), entry.getValue().toString());
                }
                String line;
                InputStream inputStream = httpURLConnection.getInputStream();
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                if (isKey) {
                    byte[] bytes = new byte[128];
                    int len;
                    len = inputStream.read(bytes);
                    isByte = true;
                    if (len == 1 << 4) {
                        keyBytes = Arrays.copyOf(bytes, 16);
                        content.append("isByte");
                    } else {
                        content.append(new String(Arrays.copyOf(bytes, len)));
                    }
                    return content;
                }
                while ((line = bufferedReader.readLine()) != null) {
                    content.append(line).append("\n");
                }
                bufferedReader.close();
                inputStream.close();
                log.info(content.toString());
                break;
            } catch (Exception e) {
                log.debug("第" + count + "次获取链接重试！\t" + urls);
                count++;
//                    e.printStackTrace();
            } finally {
                if (httpURLConnection != null) {
                    httpURLConnection.disconnect();
                }
            }
        }
        if (count > retryCount) {
            throw new M3u8Exception("连接超时！");
        }
        return content;
    }

    /**
     * 合并 url
     * 
     * @param start
     * @param end
     * @return
     */
    private String mergeUrl(String start, String end) {
        if (end.startsWith("/")) {
            end = end.replaceFirst("/", "");
        }
        int position = 0;
        String subEnd, tempEnd = end;
        while ((position = end.indexOf("/", position)) != -1) {
            subEnd = end.substring(0, position + 1);
            if (start.endsWith(subEnd)) {
                tempEnd = end.replaceFirst(subEnd, "");
                break;
            }
            ++position;
        }
        return start + tempEnd;
    }

    /**
     * 获取ts解密的密钥，并把ts片段加入set集合
     *
     * @param url     密钥链接，如果无密钥的m3u8，则此字段可为空
     * @param content 内容，如果有密钥，则此字段可以为空
     * @return ts是否需要解密，null为不解密
     */
    private String getKey(String url, StringBuilder content) {
        StringBuilder urlContent;
        if (StringUtils.isBlank(content)) {
            urlContent = getUrlContent(url, false);
        } else {
            urlContent = content;
        }
        if (!urlContent.toString().contains("#EXTM3U")) {
            throw new M3u8Exception(downloadUrl + "不是m3u8链接！");
        }
        String[] split = urlContent.toString().split("\\n");
        for (String s : split) {
            //如果含有此字段，则获取加密算法以及获取密钥的链接
            if (s.contains("EXT-X-KEY")) {
                String[] split1 = s.split(",");
                for (String s1 : split1) {
                    if (s1.contains("METHOD")) {
                        method = s1.split("=", 2)[1];
                        continue;
                    }
                    if (s1.contains("URI")) {
                        key = s1.split("=", 2)[1];
                        continue;
                    }
                    if (s1.contains("IV")) {
                        iv = s1.split("=", 2)[1];
                    }
                }
            }
        }
        String relativeUrl = url.substring(0, url.lastIndexOf("/") + 1);
        //将ts片段链接加入set集合
        for (int i = 0; i < split.length; i++) {
            String s = split[i];
            if (s.contains("#EXTINF")) {
                String s1 = split[++i];
                tsSet.add(StringUtils.isUrl(s1) ? s1 : mergeUrl(relativeUrl, s1));
            }
        }
        if (!StringUtils.isBlank(key)) {
            key = key.replace("\"", "");
            return getUrlContent(StringUtils.isUrl(key) ? key : mergeUrl(relativeUrl, key), true).toString().replaceAll("\\s+", "");
        }
        return null;
    }
}
